// =====================================================================
//
//  This file is part of the Microsoft Dynamics CRM SDK code samples.
//
//  Copyright (C) Microsoft Corporation.  All rights reserved.
//
//  This source code is intended only as a supplement to Microsoft
//  Development Tools and/or on-line documentation.  See these other
//  materials for detailed information regarding Microsoft code samples.
//
//  THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//  PARTICULAR PURPOSE.
//
// =====================================================================
namespace Microsoft.Xrm.Sdk
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.Serialization;
    using System.Security;
    using Microsoft.Xrm.Sdk.Client;
    using Microsoft.Xrm.Sdk.Metadata;
    using Microsoft.Xrm.Sdk.Query;

#if !SILVERLIGHT
    using Windows.Storage;
    using System.Collections;
#endif

	/// <summary>
	/// Class to centralize known types for the SDK contracts
	/// </summary>
	internal static class KnownTypesProvider
	{
		#region Fields
		private static object _lockObj;
		private static List<Assembly> _knownAssemblies;
		private static Dictionary<string, Type> _knownCustomValueTypes;
		private static bool _regenerateknownCustomValueTypes;
		private static Dictionary<string, Type> _knownOrganizationRequestResponseTypes;
		private static bool _regenerateknownOrganizationRequestResponseTypes;
		#endregion

		#region Constructors
		[SecuritySafeCritical]
		static KnownTypesProvider()
		{
			_lockObj = new object();
			_knownAssemblies = null;
			_knownCustomValueTypes = null;
			_regenerateknownCustomValueTypes = false;
			_knownOrganizationRequestResponseTypes = null;
			_regenerateknownOrganizationRequestResponseTypes = false;
		}

		#endregion

		private static List<Assembly> KnownAssemblies
		{
			get
			{
				if (_knownAssemblies == null)
				{
					lock (_lockObj)
					{
						if (_knownAssemblies == null)
						{
							_knownAssemblies = new List<Assembly>();

                            Assembly[] assemblies = null;
#if SILVERLIGHT
							assemblies = AppDomain.CurrentDomain.GetAssemblies();
#else
                            // WinRT does not allow inspection of assemblies in appdomain. As a workaround, load assemblies from disk and inspect them.
                            StorageFolder installFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;
                            IReadOnlyList<StorageFile> installedFiles = installFolder.GetFilesAsync().AsTask().Result; // Load synchronously. Returned value is cached.

                            List<Assembly> installedDlls = new List<Assembly>();
                            foreach (StorageFile file in installedFiles)
                            {
                                if (file.FileType == ".dll")
                                {
                                    string fileName = file.Name.Substring(0, file.Name.Length - file.FileType.Length);
                                    try
                                    {
                                        Assembly assembly = Assembly.Load(new AssemblyName(fileName));
                                        installedDlls.Add(assembly);
                                    }
                                    catch (BadImageFormatException)
                                    {
                                    }
                                }
                            }
                            assemblies = installedDlls.ToArray();
#endif

							foreach (Assembly assembly in assemblies)
							{
								RegisterAssembly(assembly);
							}
						} // if
					} // lock
				} // if
				return _knownAssemblies;
			}
		}

		/// <summary>
		/// Registers the assembly if it contain the ProxyTypesAssemblyAttribute
		/// </summary>
		/// <param name="assembly"></param>
		private static void RegisterAssembly(Assembly assembly)
		{
			if (null == GetProxyTypesAttribute(assembly))
			{
				return;
			}

			// This will also ensure that the internal list is initialized.
			if (KnownAssemblies.Contains(assembly))
				return;

			lock (_lockObj)
			{
				// check again. Use the internal list.
				if (_knownAssemblies.Contains(assembly))
					return;

				_knownAssemblies.Add(assembly);
				_regenerateknownCustomValueTypes = true;
				_regenerateknownOrganizationRequestResponseTypes = true;
			}
		}

		/// <summary>
		/// Extract the ProxyTypesAssemblyAttribute on the assembly
		/// </summary>
		/// <param name="assembly"></param>
		/// <returns>attribute if it is defined. null otherwise</returns>
		private static ProxyTypesAssemblyAttribute GetProxyTypesAttribute(Assembly assembly)
        {
#if SILVERLIGHT
            object[] ptaAttributes = assembly.GetCustomAttributes(typeof(ProxyTypesAssemblyAttribute), false);
			if ((ptaAttributes == null) || (ptaAttributes.Length == 0))
			{
				return null;
			}
			return ptaAttributes[0] as ProxyTypesAssemblyAttribute;
#else
            IEnumerable<Attribute> ptaAttributes = assembly.GetCustomAttributes(typeof(ProxyTypesAssemblyAttribute));
            if ((ptaAttributes == null) || (ptaAttributes.Count() == 0))
            {
                return null;
            }
            return ptaAttributes.ElementAt(0) as ProxyTypesAssemblyAttribute;
#endif
        }

		internal static Dictionary<string, Type> KnownCustomValueTypes
		{
			get
			{
				// Reflect on the knownassemblies if the knowncustomvaluetypes have not been initialized (or) if the knownassemblies list has been updated.
				if (_knownCustomValueTypes == null || _regenerateknownCustomValueTypes)
				{
					lock (_lockObj)
					{
						if (_knownCustomValueTypes == null || _regenerateknownCustomValueTypes)
						{
                            Assembly executingAssembly = null;
#if SILVERLIGHT
                            executingAssembly = Assembly.GetExecutingAssembly();
#else
                            executingAssembly = typeof(KnownTypesProvider).GetTypeInfo().Assembly;
#endif

                            List<Assembly> knownAssemblies = KnownAssemblies;
							Dictionary<string, Type> valueTypesMap = new Dictionary<string, Type>();

							foreach (Assembly assembly in knownAssemblies)
							{
                                if (assembly == executingAssembly)
                                {
                                    continue; // do not add types from this assembly
                                }

								// Consider only the assemblies that have shareable data contracts.
								// Non-sandboxed plugin assemblies containing data contracts (early bound) should not be added to the list, since
								// Early bound entity types are processed by ProxySerializationSurrogate class and converted to late bound types
								ProxyTypesAssemblyAttribute proxyAttribute = GetProxyTypesAttribute(assembly);
								if (proxyAttribute == null || !proxyAttribute.ContainsSharedContracts)
								{
									continue;
                                }

#if SILVERLIGHT
								foreach (Type type in assembly.GetExportedTypes())
                                {
									bool isContract = false;
									object[] dcAttributes = type.GetCustomAttributes(typeof(DataContractAttribute), false);
									if ((dcAttributes != null) && (dcAttributes.Length > 0))
									{
										isContract = true;
									}
									else
									{
										object[] cdcAttributes = type.GetCustomAttributes(typeof(CollectionDataContractAttribute), false);
										if ((cdcAttributes != null) && (cdcAttributes.Length > 0))
										{
											isContract = true;
										}
									}

									if (isContract)
									{
										if (!type.IsSubclassOf(typeof(OrganizationRequest))
											&& !type.IsSubclassOf(typeof(OrganizationResponse)))
										{
											if (!valueTypesMap.ContainsKey(type.Name))
											{
												valueTypesMap.Add(type.Name, type);
												valueTypesMap.Add(string.Format(CultureInfo.InvariantCulture, "ArrayOf{0}", type.Name), type.MakeArrayType());
												valueTypesMap.Add(string.Format(CultureInfo.InvariantCulture, "ArrayOfArrayOf{0}", type.Name), type.MakeArrayType().MakeArrayType());
											}
										}
									}
								} // foreach
#else
                                foreach (Type type in assembly.ExportedTypes)
                                {
                                    TypeInfo typeInfo = type.GetTypeInfo();

                                    bool isContract = false;
                                    IEnumerable<DataContractAttribute> dcAttributes = typeInfo.GetCustomAttributes<DataContractAttribute>(false);
                                    if ((dcAttributes != null) && (dcAttributes.Count() > 0))
                                    {
                                        isContract = true;
                                    }
                                    else
                                    {
                                        IEnumerable<CollectionDataContractAttribute> cdcAttributes = typeInfo.GetCustomAttributes<CollectionDataContractAttribute>(false);
                                        if ((cdcAttributes != null) && (cdcAttributes.Count() > 0))
                                        {
                                            isContract = true;
                                        }
                                    }

                                    if (isContract)
                                    {
                                        if (!typeInfo.IsSubclassOf(typeof(OrganizationRequest))
                                            && !typeInfo.IsSubclassOf(typeof(OrganizationResponse)))
                                        {
                                            if (!valueTypesMap.ContainsKey(type.Name))
                                            {
                                                valueTypesMap.Add(type.Name, type);
                                                valueTypesMap.Add(string.Format(CultureInfo.InvariantCulture, "ArrayOf{0}", type.Name), type.MakeArrayType());
                                                valueTypesMap.Add(string.Format(CultureInfo.InvariantCulture, "ArrayOfArrayOf{0}", type.Name), type.MakeArrayType().MakeArrayType());
                                            }
                                        }
                                    }
                                } // foreach
#endif								

							} // foreach
							_knownCustomValueTypes = valueTypesMap;
							_regenerateknownCustomValueTypes = false;
						} // if
					} // lock
				} // if

				return _knownCustomValueTypes;
			}
		}

		internal static Dictionary<string, Type> KnownOrganizationRequestResponseTypes
		{
			get
			{
				// Reflect on the knownassemblies if the knowncustomvaluetypes have not been initialized (or) if the knownassemblies list has been updated.
				if (_knownOrganizationRequestResponseTypes == null || _regenerateknownOrganizationRequestResponseTypes)
				{
					lock (_lockObj)
					{
						if (_knownOrganizationRequestResponseTypes == null || _regenerateknownOrganizationRequestResponseTypes)
						{
							List<Assembly> knownAssemblies = KnownAssemblies;
							Dictionary<string, Type> dictionary = new Dictionary<string, Type>();
							foreach (Assembly assembly in knownAssemblies)
                            {
#if SILVERLIGHT
								foreach (Type type in assembly.GetExportedTypes())
								{
									object[] dcAttributes = type.GetCustomAttributes(typeof(DataContractAttribute), false);
									if ((dcAttributes != null) && (dcAttributes.Length > 0) &&
										(type.IsSubclassOf(typeof(OrganizationRequest)) || type.IsSubclassOf(typeof(OrganizationResponse))))
									{
										foreach (DataContractAttribute dcAttribute in dcAttributes)
										{
											string name = QualifiedName((dcAttribute.Name ?? type.Name), dcAttribute.Namespace);
											if (!dictionary.ContainsKey(name))
											{
												dictionary.Add(name, type);
											}
										}
									}
								} // foreach
#else
                                foreach (Type type in assembly.ExportedTypes)
                                {
                                    TypeInfo typeInfo = type.GetTypeInfo();
                                    IEnumerable<DataContractAttribute> dcAttributes = typeInfo.GetCustomAttributes<DataContractAttribute>(false);
                                    if ((dcAttributes != null) && (dcAttributes.Count() > 0) &&
                                        (typeInfo.IsSubclassOf(typeof(OrganizationRequest)) || typeInfo.IsSubclassOf(typeof(OrganizationResponse))))
                                    {
                                        foreach (DataContractAttribute dcAttribute in dcAttributes)
                                        {
                                            string name = QualifiedName((dcAttribute.Name ?? type.Name), dcAttribute.Namespace);
                                            if (!dictionary.ContainsKey(name))
                                            {
                                                dictionary.Add(name, type);
                                            }
                                        }
                                    }
                                } // foreach
#endif
                            } // foreach
							_knownOrganizationRequestResponseTypes = dictionary;
							_regenerateknownOrganizationRequestResponseTypes = false;
						} // if
					} // lock
				} // if

				return _knownOrganizationRequestResponseTypes;
			}
		}


		// returns a list of types valid for value parameter in a property bag
		[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by runtime to get known types")]
		public static IEnumerable<Type> RetrieveKnownValueTypes()
		{
			List<System.Type> knownTypes = new List<System.Type>();
			AddKnownAttributeTypes(knownTypes);
			knownTypes.Add(typeof(AliasedValue));
			knownTypes.Add(typeof(Dictionary<string, string>));
			knownTypes.Add(typeof(Entity));
			knownTypes.Add(typeof(Entity[]));
			knownTypes.Add(typeof(ColumnSet));
			knownTypes.Add(typeof(EntityReferenceCollection));
			knownTypes.Add(typeof(QueryBase));
			knownTypes.Add(typeof(QueryExpression));
			knownTypes.Add(typeof(QueryExpression[]));
			knownTypes.Add(typeof(LocalizedLabel[]));
			knownTypes.Add(typeof(PagingInfo));
			knownTypes.Add(typeof(Relationship));
			knownTypes.Add(typeof(AttributePrivilegeCollection));
			knownTypes.Add(typeof(RelationshipQueryCollection));

			Dictionary<string, Type> knownCustomValueTypes = KnownCustomValueTypes;
			foreach (Type t in knownCustomValueTypes.Values)
			{
				knownTypes.Add(t);
			}

			return knownTypes;
		}

		[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by runtime to get known types")]
		public static IEnumerable<Type> RetrieveKnownConditionValueTypes()
		{
			List<System.Type> knownTypes = new List<System.Type>();
			AddKnownAttributeTypes(knownTypes);
			return knownTypes;
		}

		[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by other methods of this class")]
		internal static void AddKnownAttributeTypes(List<System.Type> knownTypes)
		{
			knownTypes.Add(typeof(bool));
			knownTypes.Add(typeof(bool[]));
			knownTypes.Add(typeof(int));
			knownTypes.Add(typeof(int[]));
			knownTypes.Add(typeof(string));
			knownTypes.Add(typeof(string[]));
			knownTypes.Add(typeof(string[][]));
			knownTypes.Add(typeof(double));
			knownTypes.Add(typeof(double[]));
			knownTypes.Add(typeof(decimal));
			knownTypes.Add(typeof(decimal[]));
			knownTypes.Add(typeof(Guid));
			knownTypes.Add(typeof(Guid[]));
			knownTypes.Add(typeof(DateTime));
			knownTypes.Add(typeof(DateTime[]));
			knownTypes.Add(typeof(Money));
			knownTypes.Add(typeof(Money[]));
			knownTypes.Add(typeof(EntityReference));
			knownTypes.Add(typeof(EntityReference[]));
			knownTypes.Add(typeof(OptionSetValue));
			knownTypes.Add(typeof(OptionSetValue[]));
			knownTypes.Add(typeof(EntityCollection));
			knownTypes.Add(typeof(Money));
			knownTypes.Add(typeof(Label));
			knownTypes.Add(typeof(LocalizedLabel));
			knownTypes.Add(typeof(LocalizedLabelCollection));
			knownTypes.Add(typeof(Metadata.EntityMetadata[]));
			knownTypes.Add(typeof(Metadata.EntityMetadata));
			knownTypes.Add(typeof(Metadata.AttributeMetadata[]));
			knownTypes.Add(typeof(Metadata.AttributeMetadata));
			knownTypes.Add(typeof(Metadata.RelationshipMetadataBase[]));
			knownTypes.Add(typeof(Metadata.RelationshipMetadataBase));
			knownTypes.Add(typeof(Metadata.EntityFilters));
			knownTypes.Add(typeof(Metadata.OptionSetMetadataBase));
			knownTypes.Add(typeof(Metadata.OptionSetMetadataBase[]));
			knownTypes.Add(typeof(Metadata.OptionSetMetadata));
			knownTypes.Add(typeof(Metadata.BooleanOptionSetMetadata));
			knownTypes.Add(typeof(Metadata.OptionSetType));
			knownTypes.Add(typeof(Metadata.ManagedPropertyMetadata));
			knownTypes.Add(typeof(Metadata.ManagedPropertyMetadata[]));
			knownTypes.Add(typeof(BooleanManagedProperty));
			knownTypes.Add(typeof(AttributeRequiredLevelManagedProperty));
		}

		internal static string QualifiedName(string typeName, string typeNamespace)
		{
			return String.Format(CultureInfo.InvariantCulture, "{0}/{1}", typeNamespace, typeName);
		}
	}
}
